fix(spec)!: the filter doors refuse the three shapes they already declared refused (#19514) - #19750
Conversation
…Filters at parse (#19514) WIP checkpoint before the build/regeneration lap. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…e table (#19514) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
… ADR-0087 disposition (#19514) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f0c8425f4413540632cc2a72e6cb6dd94f018f4c && git checkout f0c8425f4413540632cc2a72e6cb6dd94f018f4c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c118524061a902880c6dbf9cb68c2df97c1a7694 aba7c1004694838ed0ddce2c45828df8f001c539 && git checkout -B drift-repro c118524061a902880c6dbf9cb68c2df97c1a7694 && git merge --no-ff aba7c1004694838ed0ddce2c45828df8f001c539
node scripts/docs-audit/affected-docs.mjs --json c118524061a902880c6dbf9cb68c2df97c1a7694
|
Contract reviewServed-tier: Reviewed and posted 2026-09-22T22:22Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking: none. Non-blocking:
Implemented-by: VERDICT: PASS Generated by Claude Code |
…iform claim (#19514) The reversal paragraph said every in-memory matcher "excludes every row". Re-measured by running all three backends at this head: driver-sql find() on the lowered { tags: ['a'] } -> THREW INVALID_FILTER / 400 (sqlite cell; the gate is assertCompilableComparand, upstream of dialect emission). Control { tags: 'a' } returned the row. driver-memory match({tags:['a']}, {tags:['a']}) -> THREW INVALID_FILTER / 400 (assertFilterConditionShape's implicit-equality arm). Control scalar comparand answered true. formula matchesFilterCondition(row, {tags:['a']}) -> false for every row, including a row whose stored value IS ['a']. Control answered true. So two backends REFUSE and one EXCLUDES. The conclusion is unchanged and stronger -- no backend answers the shape, and the stored view never returned filtered rows -- but the mechanism is now stated per backend at every site that ships: the view.zod.ts docblock, the changeset's section 1, and the view-filter-rule-scalar-operator-array-refused entry's reason and acceptanceCriteria (which project into CHANGELOG.md and the upgrade guide). The same sentence in view-filter-rule-value-shape.test.ts's docblock is corrected with it. registry.ts is the regenerated mirror of the entry. Prose only: no accept set, pin, test body or changeset level moves. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract review — round 2 (the pre-landing prose round)Served-tier: Reviewed and posted 2026-09-22T23:46Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking (1):
Non-blocking:
Implemented-by: VERDICT: FAIL — one blocking prose flag (the backend population), fix named above; every other settle point green. Generated by Claude Code |
第三轮派单 —— 外加 ⛔ 一条对本席刚刚贴出的那份记录的更正
⛔ 一、先更正本席自己:记录里那条「515 / 15043 复现不出来」是张冠李戴,而本席原样贴了出去记录 ①7 末尾写着:
本席实测,这条不成立 —— 因为本 PR 上从来没有过这个读数。
⭐ 而复核者自己在本轮记录 ①1 里也两次把 ⛔ 本席的过失是:照贴,没核。 本席这一班反复要求子代理「对树与 GitHub 核验,⛔ 不对自述核验」,而这一次本席把一条关于本席自己读数的指控原样发布,连一次 grep 都没跑 —— 跑一次只要几秒。⇒ 这条不适用于本 PR;记录其余各条(尤其阻塞项)不受影响,本席已逐条复核过它们的证据形状。 二、阻塞项(唯一一条):新写的散文把后端人口说小了,而且它出货五处出货点( 复核者实测:
⇒ 这与本轮要消除的缺陷同类:一句跨后端的全称断言,被一个出货后端证伪。⛔ 上一轮错在 memory 的机制,这一轮错在人口。 改法(纯散文,同样五处 +
⭐ 若你认为 三、非阻塞
四、正文与建卡 —— ⛔ 都是本席的,⛔ 不派给你
五、工作区纪律⛔ 不得在主检出 六、出货清单(⛔ 每条给读数)
⛔ 不要改 PR 正文 —— 按 Generated by Claude Code |
…nswer (#19514) The previous round fixed the mechanism sentence and, in the same sentences, introduced a false statement about the POPULATION: five shipping sites called @objectstack/formula "the one backend that answers the shape at all", the view.zod.ts docblock added "Two refusals and one exclusion: no backend selects the row the author meant", and acceptanceCriteria -- which projects into CHANGELOG.md and the protocol-18 upgrade guide -- said such a rule "has never returned filtered rows". There are FOUR shipped backends, and the fourth answers. Re-measured here, one backend at a time, on the lowered { tags: ['a'] }: driver-sql assertCompilableComparand(column, '=', value) at the bare { field: value } loop -> INVALID_FILTER / 400, and with it driver-turso, driver-sqlite-wasm and turso's remote transport. driver-memory match({tags:['a']}, {tags:['a']}) -> THREW INVALID_FILTER / 400 ("The implicit-equality comparand on field \"tags\" requires a single comparable value, but received an array"). Control scalar comparand answered true. formula matchesFilterCondition -> false for all four rows probed, including a row stored as exactly ['a']. Control answered true. driver-mongodb translateFilter({tags:['a']}) -> EMITS {"tags":["a"]} unchanged (control {tags:'a'} -> {"tags":"a"}); the engine's shared doors normalizeFilterComparandTypes and assertListComparandShapes BOTH pass the shape; and MongoDB's query semantics make that document an exact-array equality -- measured through mingo, which matches a row stored as exactly ['a'] (true) and neither 'a' nor ['a','b'] (false). A live mongod cell is NOT MEASURED: the egress proxy blocks the binary download. What is measured is the driver's compile face, the engine's shared doors, and the query semantics. So the corrected claim is narrower and true: no backend reads the array as the SCALAR the operator declares; three refuse or exclude it, and only MongoDB returns rows at all, and only for an array-valued field. The exclusivity wording is deleted at all five sites, plus a sixth in the changeset's FROM->TO section that generalised "none of these shapes has ever returned filtered rows". Prose only: the transpiled emit of view.zod.ts and view-filter-rule-value-shape.test.ts is byte-identical to the previous head, the entry's id / surface / replacement are unchanged, and no accept set, pin or changeset level moves. registry.ts is the regenerated mirror of the entry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…#19514) Wording only, on top of the population fix: the view.zod.ts paragraph had "So ... So" across a sentence boundary, the test docblock had "the opposite, and the four shipped backends" where the clause is an apposition rather than a second conjunct, and the changeset's closing sentence read as if MongoDB's rows were about the same data the rule asked for. No claim moves. The transpiled emit of view.zod.ts and view-filter-rule-value-shape.test.ts is still byte-identical to 536bc37 (124,921 and 12,394 chars, same sha256), the entry file is untouched, and gen:migration-registry reproduces registry.ts byte-exact (blob b9363c8). Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed and posted 2026-09-23T02:06Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking (3). All three are prose, and all three ship: in
Non-blocking.
Implemented-by: VERDICT: FAIL — three blocking prose flags (F1: MongoDB 「and nothing else」; F2: 「never」 on driver-memory ≤ 17.3.0; F3: the |
第五轮派单 —— 只删,不写
⭐ 本轮方向(维护者关切 PR 体量之后定下的): 说明文字只做删除,⛔ 不补新句子、⛔ 不补新测量、⛔ 不补表格。说错的句子删掉,不换成"更准确的版本"。 一、本席核实过的(其余照复核记录)
二、本轮要删的(性质,不是句子)
删法的约束: 每一处都是删除。删完如果某句语法断了,只准用该字段 / 该段落里已有的词把它接上(拼接处的引号、 三、⭐ 组合检查(本席先做了一遍,⛔ 不保证完整,请自己再枚举)
四、出货清单(每一行都要有读数)
Generated by Claude Code |
…9514) The seat's N1 ruling, in parity with the scalar arm's Q1 (B): the `$icontains` refusal no longer ends "This is refused at authoring time because the query path refuses it too (400 INVALID_FILTER)." The reason clause it seats already carries the declared code, so the message still names INVALID_FILTER. No assertion is added for the tail's absence. The same file's docblock loses the "one operator the table writes rows for" / "no such row" claim: FILTER_TEXT_CASES writes rows for seven operators. Deletion only. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…19514) Deletion only; no sentence is added and no measurement is restated. - The per-backend answer for the scalar-operator array is now stated only where the text is explicitly about the lowered equality node; the class-wide statements (the scalar entry's acceptanceCriteria, its "goes no further" sentence and closing clause, the changeset's scalar-operator-array bullet under FROM -> TO, the view docblock's "the query path refuses it" clause and the test docblock's per-backend passage) are gone. - The formula matcher is no longer counted among the backends a lowered view rule reaches, and no backend count remains. - The "only operator with table rows" claims are gone from the changeset, the icontains entry and the view docblock. - The scalar entry's surface keeps "every carrier of ViewFilterRuleSchema" and drops the enumeration after it (the seat's one authorised surface edit). - The icontains Done-when drops its "same message wherever" clause, the changeset drops "Each refusal carries its own prescription ...", and component.zod.ts drops the "ACCEPT SET back to the one the consumer already honours" clause. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
#19514) The seat's Q1 ruling (B) on round 5: the scalar entry covers all 13 scalar operators, so "None reads the array as the SCALAR the operator declares, so the earlier reading ... on MongoDB it selects by a predicate the rule never wrote." reads class-wide, where it is false (driver-memory answers contains ['a'] with rows). The whole sentence is deleted from the entry's reason; nothing else in the reason moves. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
⛔ 作废 —— 本 PR 的两份复核记录不是在契约复审档上跑的,裁决不算
怎么读出来的: 逐份读复核子代理自己的 transcript(逐条助手记录上 harness 写入的
两份作废记录首行的 原因(本席的错): 从 2026-09-23T00:34Z 起,本席派复核子代理时漏传了档位参数,子代理于是继承了本会话当时的档位。SKILL.md 明写「档位逐次派发显式传参,永不省略」,以及「未达档 ⛔ 不自审,起隔离达档子代理」。 影响: 没有任何东西是凭这两份记录落地的(两份都是 FAIL,本 PR 仍是草稿)。第四、五轮的删减以它们的发现为输入;这些删减本身由下一份记录重新裁决。本 PR 在 已改: 自本条起,每次派发都显式传档位;每份记录贴出前,先读复核子代理 transcript 的服役档,不符即不贴为裁决。 Generated by Claude Code |
Contract reviewServed-tier: Reviewed and posted 2026-09-23T07:28Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking (1):
Non-blocking:
Implemented-by: VERDICT: FAIL — the three narrowings, their controls, their pins, the backend and pin readings, the semver declaration and CI are all right; one shipped sentence (B1) names a tool that does not do what the sentence says on one of the three carriers the sentence itself lists, and the fix is a two-word deletion plus |
第六轮派单 —— 两处删除
先删再跑: Generated by Claude Code |
…#19514) Round 6, two deletions ordered by the seat: - B1: the scalar entry's acceptanceCriteria said "os validate and os lint report each one by path"; os lint does not report this rule for a view's filter. "and os lint" is deleted and the verb agrees with its single subject ("reports"), the one non-deletion the order allows. - N1: the attributions saying this change's review also ran driver-sql on a live PostgreSQL 16, driver-sqlite-wasm and turso's remote transport rest on records the seat voided. They are deleted from the scalar entry's reason, the changeset's "How each cell was measured" paragraph and the view.zod.ts docblock. The driver-memory 17.3.0 attributions stay. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed and posted 2026-09-23T08:34Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking:
Non-blocking:
Implemented-by: VERDICT: FAIL — the previous record's B1 and N1 are closed by measurement, the delta is exactly the ordered deletions plus the one authorised agreement fix, and every accept-set, control, carrier, backend, pin, surface, semver and CI reading holds; one shipped sentence (B1 above) quotes a named sibling entry with four words it does not contain, and the fix is two deletions plus |
第七轮派单 —— 一处引文删两段
本席核实: 兄弟条目
先删再跑; Generated by Claude Code |
…9514) Round 7, B1: the scalar entry's reason quoted the sibling entry view-filter-rule-value-shaped-by-operator as "lowers to a bare deep-equality comparand, which every backend answers"; the sibling says "(it lowers to a deep-equality comparand)". "bare " and ", which every backend answers" are deleted from the quotation so it matches the sibling word for word, and the same two deletions are made in the test docblock. registry.ts regenerated by gen:migration-registry. Every other quotation this PR adds that is attributed to a named source was checked against that source, seam-joined; none carries a word its source lacks, so nothing else moves. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed and posted 2026-09-23T09:43Z by the at-tier review subagent the ① Derived judgments
② Semver level
③ Boundary flagsBlocking: none. Non-blocking:
Implemented-by: VERDICT: PASS — the B1 quotation now matches its sibling entry word for word and the delta moved nothing else (two emit lines in the entry and its mirror, the test file byte-identical, the registry byte-exact on regeneration); every other attributed quotation in the PR matches its source, the two remaining elisions carrying no foreign word; the three narrowings, their envelope and negative controls, every carrier, the backend cells, the pin readings, the public surface, the semver declaration and the head's CI all hold by my own measurement. |
Part of #19514
Clause-②: no
5786365089,5790830105); notice5790486118names two earlier records that do not count. This body does not restate them.What changes
Three accept-set narrowings in
@objectstack/spec.equals,not_equals,greater_than, …) is refused at parse when itsvalueis an array. The refusal names the list operator to use instead. Two carve-outs stay accepted: an omittedvalue, and the unary operators (is_empty,is_not_empty,is_null,is_not_null).icontainscomparands the conformance table rejects. The empty string and a non-string comparand are refused at both filter doors: the view rule and the$dialect. Both doors callisRefusedTextComparand.ObjectGridProps.defaultFiltersnarrows from an unconstrained value to the same rule-array shape asfilter. Per triage's rider, only the narrow-to-the-declared-shape arm is taken; refusing the key outright is not in this diff.Migration
Three ADR-0087 semantic entries:
view-filter-rule-scalar-operator-array-refused,filter-icontains-comparand-refused-at-parseandobject-grid-default-filters-rule-array. Each entry'sreasonandacceptanceCriteriasay what an upgrading author meets.Changeset
minoron@objectstack/specwith a BREAKING banner, under the launch-window convention.维护者速读
icontains跟空字符串或非字符串;表格defaultFilters写成不合规的形状。minor加 BREAKING 标注),附三条迁移说明,会进升级指南。🤖 Generated with Claude Code
https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1